// Copyright (C) 1999-2001 by Jason Hunter <jhunter_AT_acm_DOT_org>.
// All rights reserved.  Use of this class is limited.
// Please see the LICENSE for more information.

package com.baomidou.framework.upload.multipart;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.servlet.ServletInputStream;

/**
 * A <code>FilePart</code> is an upload part which represents a
 * <code>INPUT TYPE="file"</code> form parameter. Note that because file upload
 * data arrives via a single InputStream, each FilePart's contents must be read
 * before moving onto the next part. Don't try to store a FilePart object for
 * later processing because by then their content will have been passed by.
 * 
 * @author Geoff Soutter
 * @version 1.2, 2001/01/22, getFilePath() addition thanks to Stefan Eissing
 * @version 1.1, 2000/11/26, writeTo() bug fix thanks to Mike Shivas
 * @version 1.0, 2000/10/27, initial revision
 */
public class FilePart extends Part {

	/** "file system" name of the file */
	private String fileName;

	/** path of the file as sent in the request, if given */
	private String filePath;

	/** content type of the file */
	private String contentType;

	/** input stream containing file data */
	private PartInputStream partInput;

	/** file rename policy */
	private FileRenamePolicy policy;

	/**
	 * Construct a file part; this is called by the parser.
	 * 
	 * @param name
	 *            the name of the parameter.
	 * @param in
	 *            the servlet input stream to read the file from.
	 * @param boundary
	 *            the MIME boundary that delimits the end of file.
	 * @param contentType
	 *            the content type of the file provided in the MIME header.
	 * @param fileName
	 *            the file system name of the file provided in the MIME header.
	 * @param filePath
	 *            the file system path of the file provided in the MIME header
	 *            (as specified in disposition info).
	 * 
	 * @exception IOException
	 *                if an input or output exception has occurred.
	 */
	FilePart(String name, ServletInputStream in, String boundary, String contentType, String fileName, String filePath)
			throws IOException {
		super(name);
		this.fileName = fileName;
		this.filePath = filePath;
		this.contentType = contentType;
		partInput = new PartInputStream(in, boundary);
	}

	/**
	 * Puts in place the specified policy for handling file name collisions.
	 */
	public void setRenamePolicy(FileRenamePolicy policy) {
		this.policy = policy;
	}

	/**
	 * Returns the name that the file was stored with on the remote system, or
	 * <code>null</code> if the user didn't enter a file to be uploaded. Note:
	 * this is not the same as the name of the form parameter used to transmit
	 * the file; that is available from the <code>getName</code> method. Further
	 * note: if file rename logic is in effect, the file name can change during
	 * the writeTo() method when there's a collision with another file of the
	 * same name in the same directory. If this matters to you, be sure to pay
	 * attention to when you call the method.
	 * 
	 * @return name of file uploaded or <code>null</code>.
	 * 
	 * @see Part#getName()
	 */
	public String getFileName() {
		return fileName;
	}

	/**
	 * Returns the full path and name of the file on the remote system, or
	 * <code>null</code> if the user didn't enter a file to be uploaded. If path
	 * information was not supplied by the remote system, this method will
	 * return the same as <code>getFileName()</code>.
	 *
	 * @return path of file uploaded or <code>null</code>.
	 *
	 * @see Part#getName()
	 */
	public String getFilePath() {
		return filePath;
	}

	/**
	 * Returns the content type of the file data contained within.
	 * 
	 * @return content type of the file data.
	 */
	public String getContentType() {
		return contentType;
	}

	/**
	 * Returns an input stream which contains the contents of the file supplied.
	 * If the user didn't enter a file to upload there will be <code>0</code>
	 * bytes in the input stream. It's important to read the contents of the
	 * InputStream immediately and in full before proceeding to process the next
	 * part. The contents will otherwise be lost on moving to the next part.
	 * 
	 * @return an input stream containing contents of file.
	 */
	public InputStream getInputStream() {
		return partInput;
	}

	/**
	 * Write this file part to a file or directory. If the user supplied a file,
	 * we write it to that file, and if they supplied a directory, we write it
	 * to that directory with the filename that accompanied it. If this part
	 * doesn't contain a file this method does nothing.
	 *
	 * @return number of bytes written
	 * @exception IOException
	 *                if an input or output exception has occurred.
	 */
	public long writeTo(File fileOrDirectory) throws IOException {
		long written = 0;

		OutputStream fileOut = null;
		try {
			// Only do something if this part contains a file
			if (fileName != null) {
				// Check if user supplied directory
				File file;
				if (fileOrDirectory.isDirectory()) {
					// Write it to that dir the user supplied,
					// with the filename it arrived with
					file = new File(fileOrDirectory, fileName);
				} else {
					// Write it to the file the user supplied,
					// ignoring the filename it arrived with
					file = fileOrDirectory;
				}
				if (policy != null) {
					file = policy.rename(file);
					fileName = file.getName();
				}
				fileOut = new BufferedOutputStream(new FileOutputStream(file));
				written = write(fileOut);
			}
		} finally {
			if (fileOut != null)
				fileOut.close();
		}
		return written;
	}

	/**
	 * Write this file part to the given output stream. If this part doesn't
	 * contain a file this method does nothing.
	 *
	 * @return number of bytes written.
	 * @exception IOException
	 *                if an input or output exception has occurred.
	 */
	public long writeTo(OutputStream out) throws IOException {
		long size = 0;
		// Only do something if this part contains a file
		if (fileName != null) {
			// Write it out
			size = write(out);
		}
		return size;
	}

	/**
	 * Internal method to write this file part; doesn't check to see if it has
	 * contents first.
	 *
	 * @return number of bytes written.
	 * @exception IOException
	 *                if an input or output exception has occurred.
	 */
	long write(OutputStream out) throws IOException {
		// decode macbinary if this was sent
		if (contentType.equals("application/x-macbinary")) {
			out = new MacBinaryDecoderOutputStream(out);
		}
		long size = 0;
		int read;
		byte[] buf = new byte[8 * 1024];
		while ((read = partInput.read(buf)) != -1) {
			out.write(buf, 0, read);
			size += read;
		}
		return size;
	}

	/**
	 * Returns <code>true</code> to indicate this part is a file.
	 * 
	 * @return true.
	 */
	public boolean isFile() {
		return true;
	}
}
